
// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

#include <type_traits>

#include "scheduler_basic.hpp"


namespace scheduler_basic {

    /**
     * @brief 集成消抖功能的单按键轮询监听工具，只支持低电平有效的按键。
     *
     * @tparam TimeType                    默认参数假设时间单位是毫秒，如果不是，需要调整按键扫描周期、长按时间、双击间隔时间等参数
     * @tparam LongPressThreshold          按键按下后，经过指定次数的轮询还没抬起，则触发长按事件。参数值要考虑消抖所需的额外轮询次数，若长按次数为6，而滤波参数要求8 次1 才能抬起按键，则每次按键都会触发长按
     * @tparam DoublePressThreshold        一次click 后，若在等待时间内按键再次按下，触发double_press 事件；若按键抬起后触发了第二次click，则与第二次click 同时触发双击事件。参数值同样要考虑消抖所需的轮询次数。
     * @tparam ScanPeriod                  默认轮询周期8ms，忽略定时任务的延迟，扫描周期是125Hz
     * @tparam FilterMask                  默认值0x003f，表示只关心ff 对应的低6 位，即连续6 次读按键的电平。若6 次全为0，则判定按键按下；反之，若全为1，则为抬起
     */
    template <typename TimeType,
              typename PinType,
              uint16_t LongPressThreshold = 120,
              uint16_t DoublePressThreshold = 30,
              TimeType ScanPeriod = 8,
              uint16_t FilterMask = 0x003f>
    class ButtonListener : TaskBase<TimeType> {
       private:
        PinType _button_pin;
        uint16_t _flag_bits = 0;
        uint16_t _long_press_counter = 0;
        uint16_t _double_click_waiting_counter = 0;
        uint16_t _filter_buffer = 0xffff;


        // 每个枚举值对应_flag_bits 里的一个位，用于存储标志。用这种方式而不是位域，原因是这样更方便同时操作多个标志位。
        enum class _flag {
            pressed = 0,
            double_click_before_release,
            running,

            // 有button 前缀的标志位可以称为“按键信号”或“按键事件”，由调用者在响应事件后随时清零。
            // 除了button_long_pressing 标志，其他标志扫描函数只负责置位，不自动清零
            // 没有前缀的标志，如pressed，用于支持内部逻辑，调用者不能直接控制
            button_pressed,
            button_released,
            button_clicked,

            button_double_pressed,
            button_double_clicked,
            button_double_press_timeout,  // 按键抬起后自动清零，在阈值时间内没有发生双击，则触发双击超时事件，

            button_long_pressed,
            button_long_press_released,
            button_long_pressing,  // 在长按发生时置位，长按抬起时自动清零

            // 一共 12 个标志位，可以放进uint16_t 里
        };


        constexpr auto _flag_to_num(_flag f) {
            return static_cast<std::underlying_type_t<decltype(f)>>(f);
        }


        constexpr uint16_t _calc_flag_mask_sum(_flag f) {
            return (1 << _flag_to_num(f));
        }

        template <typename... Ts>
        constexpr uint16_t _calc_flag_mask_sum(_flag f, Ts... fs) {
            return (1 << _flag_to_num(f)) | _calc_flag_mask_sum(fs...);
        }


        template <typename... Ts>
        void _set_flag(_flag f, Ts... fs) {
            _flag_bits |= _calc_flag_mask_sum(f, fs...);
        }

        template <typename... Ts>
        void _clr_flag(_flag f, Ts... fs) {
            _flag_bits &= ~_calc_flag_mask_sum(f, fs...);
        }

        auto _test_flag(_flag f) const {
            return (_flag_bits & _calc_flag_mask_sum(f));
        }

        auto _test_and_clr_flag(_flag f) {
            auto r = _test_flag(f);
            _clr_flag(f);
            return r;
        }


       public:
        ButtonListener(PinType button_pin) :
            _button_pin(button_pin) {
            _set_flag(_flag::running);
        }


        void poll_button() {
            this->_filter_buffer <<= 1;

            if (_button_pin.get()) {
                this->_filter_buffer |= 0x01;
            }

            if (!_test_flag(_flag::pressed)) {                   // 按键抬起时
                if ((this->_filter_buffer & FilterMask) == 0) {  // 连续8 次低电平，表示按键确实按下了
                    _set_flag(_flag::pressed, _flag::button_pressed);

                    // 第二次按下发生在双击等待时间内，抬起后可能触发双击
                    if (_double_click_waiting_counter != 0) {
                        _set_flag(_flag::double_click_before_release, _flag::button_double_pressed);
                        _double_click_waiting_counter = 0;
                    }

                    // 启动长按计数
                    _long_press_counter = LongPressThreshold;
                }
                else {  // 否则按键还在抬起状态，更新双击等待计数器，直到计数器归零
                    if (_double_click_waiting_counter != 0) {
                        --_double_click_waiting_counter;

                        if (_double_click_waiting_counter == 0) {
                            _set_flag(_flag::button_double_press_timeout);
                        }
                    }
                }
            }
            else {                                                  // 按键保持按下时
                if (((~this->_filter_buffer) & FilterMask) == 0) {  // 连续8 次高电平，表示按键抬起。所以一次press 至少会持续8 次扫描

                    // 启动双击等待计数器
                    _double_click_waiting_counter = DoublePressThreshold;

                    // 长按后抬起
                    if (_long_press_counter == 0) {
                        _set_flag(_flag::button_long_press_released);
                        _clr_flag(_flag::button_long_pressing);
                    }
                    else {  // 如果按下的时间没到长按的阈值，则在按键抬起时发出clicked 信号，否则忽略
                        _set_flag(_flag::button_clicked);

                        // 双击事件和第二次clicked 事件同时发出
                        if (_test_flag(_flag::double_click_before_release)) {
                            _set_flag(_flag::button_double_clicked);
                        }
                    }

                    // 抬起后清零长按计数器
                    _long_press_counter = 0;

                    // 无论如何，双击等待标志都要清零，还有双击超时事件
                    _clr_flag(_flag::pressed, _flag::double_click_before_release, _flag::button_double_press_timeout);
                    _set_flag(_flag::button_released);
                }
                else {  //  否则按键尚未抬起，更新长按计数器和长按事件
                    if (this->_long_press_counter != 0) {
                        --(this->_long_press_counter);

                        if (this->_long_press_counter == 0) {
                            _set_flag(_flag::button_long_pressed, _flag::button_long_pressing);
                        }
                    }
                }
            }
        }


        TimeType task() override {
            if (_test_flag(_flag::running)) {
                poll_button();
            }
            return ScanPeriod;
        }


        void stop_polling() {
            _clr_flag(_flag::running);
        }


        void start_polling() {
            _set_flag(_flag::running);
        }


        /**
         * @brief 完全复位内部状态，停止轮询按键
         *
         */
        void reset() {
            _flag_bits = 0;
            _long_press_counter = 0;
            _double_click_waiting_counter = 0;
            _filter_buffer = 0xffff;
        }


        /**
         * @brief 在按键抬起后重置内部状态。
         *
         * 如果在按键尚未抬起时重置状态，可能在单次点击中重复触发按键事件。比如，按键按下时触发了button_pressed 事件，
         * 然后立即重置状态，若按键没有抬起，button_pressed 事件会再置位一次。
         *
         * @return true   按键已释放，内部状态和事件标志已复位
         * @return false  按键尚未释放
         */
        bool reset_after_button_released() {
            if (_test_flag(_flag::pressed)) {
                return false;
            }
            else {
                reset();
                return true;
            }
        }


        // pressed === 事件
        auto test_pressed_flag() const {
            return _test_flag(_flag::button_pressed);
        }

        void clr_pressed_flag() {
            _clr_flag(_flag::button_pressed);
        }

        auto test_and_clr_pressed_flag() {
            return _test_and_clr_flag(_flag::button_pressed);
        }


        // released === 事件
        auto test_released_flag() const {
            return _test_flag(_flag::button_released);
        }

        void clr_released_flag() {
            _clr_flag(_flag::button_released);
        }

        auto test_and_clr_released_flag() {
            return _test_and_clr_flag(_flag::button_released);
        }


        // clicked === 事件
        auto test_clicked_flag() const {
            return _test_flag(_flag::button_clicked);
        }

        void clr_clicked_flag() {
            _clr_flag(_flag::button_clicked);
        }

        auto test_and_clr_clicked_flag() {
            return _test_and_clr_flag(_flag::button_clicked);
        }


        // double clicked === 事件
        auto test_double_clicked_flag() const {
            return _test_flag(_flag::button_double_clicked);
        }

        void clr_double_clicked_flag() {
            _clr_flag(_flag::button_double_clicked);
        }

        auto test_and_clr_double_clicked_flag() {
            return _test_and_clr_flag(_flag::button_double_clicked);
        }


        // long_pressed === 事件
        auto test_long_pressed_flag() const {
            return _test_flag(_flag::button_long_pressed);
        }

        void clr_long_pressed_flag() {
            _clr_flag(_flag::button_long_pressed);
        }

        auto test_and_clr_long_pressed_flag() {
            return _test_and_clr_flag(_flag::button_long_pressed);
        }


        // long_press_released === 事件
        auto test_long_press_released_flag() const {
            return _test_flag(_flag::button_long_press_released);
        }

        void clr_long_press_released_flag() {
            _clr_flag(_flag::button_long_press_released);
        }

        auto test_and_clr_long_press_released_flag() {
            return _test_and_clr_flag(_flag::button_long_press_released);
        }
    };

}  // namespace scheduler_basic
